前一篇我們完成了一個基本的 counter slice 和簡單的 reducer,今天我們試做 extraReducer 的部分,一樣我們簡單設定一個計分的 slice 透過綁定 counter 的 minus & plus 來做加分的動作,當然你也可以按照自己的喜好更改,那麼我們就開始吧!
首先我們新增一個 pointSlice 的檔案於 slices 資料夾內,如下:
// src/features/slices/pointSlice.ts
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "../store";
import { minus, plus } from "./counterSlice";
interface PointState {
pt: number;
}
const initialState: PointState = {
pt: 0
}
const pointSlice = createSlice({
name: 'point',
initialState,
reducers: {},
// 綁定 counter 的 actions 與之前的做法一致
extraReducers: (builder) => {
builder
.addCase(minus, (state) => {
state.pt += 2
return state
})
.addCase(plus, (state) => {
state.pt += 3
return state
})
}
})
export const selectPoint = (state: RootState) => state.point
export default pointSlice.reducer;
完成以上基本就是做些微的調整幾乎就完成了,那麼我們接著調整 store 的部分,如下:
// src/features/store.ts
import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./slices/counterSlice";
import pointSlice from "./slices/pointSlice";
const store = configureStore({
reducer: {
counter: counterSlice,
point: pointSlice,
},
})
// RootState要記得下export來提供slices使用
export type RootState = ReturnType<typeof store.getState>
export default store;
接著我們將分數顯示在畫面上,我們另外做一個 component 叫 Point,如下:
// src/components/Point.tsx
import { useSelector } from "react-redux"
import { selectPoint } from "../features/slices/pointSlice"
const Point = () => {
const point = useSelector(selectPoint)
// 這裡簡單引入就好,因為我們值的變更是綁定 counter
return (
<div className="card" style={{margin: '1rem 0'}}>
<h5>{point.pt}</h5>
</div>
)
}
export default Point
接著,於 App 引入我們剛剛製作完成的 point component:
// src/App.tsx
import Counter from "./components/Counter"
import Point from "./components/Point"
function App() {
return (
<div className="container">
<h1>Typescript Demo</h1>
<Counter/>
<Point/>
</div>
)
}
export default App
這時候畫面上的分數應該就會隨著你案的按鈕增加分數了,那麼到這裡基本的應用已經完成了,讓我們回過頭來處理 RTK query 的部分吧!
首先,移動到你 server 的資料夾下面,並啟動看看,我的話是預設在 port:8080,這部分的示範會以先前分享給各位的 nest todo Api server 為主,如果不想太麻煩的話可以考慮用 JSON server 去做簡單的 todo mock data 就好,那麼接下來的內容會簡單介紹一下 nest 裏面的內容。
在 nest 的結構裡面其實與 angular 的寫法是類似的,在 src/main.ts 為預設的入口,然後引入 app.module.ts 內的 AppModule 於 nest 框架核心 NestFactory 進行處理,在 app.module.ts 裏面會看到對應的 TodosModule 透過 todo.module.ts 裏面又可以看到另外引入的 controller,最後你會發現我們的 restApi 就是在 todo.controller.ts 進行撰寫的。
如果想要更深入理解 nest 的話持續關注我,也許明年或是之後有時間會出一系列文章介紹這個框架的用法,當然也可以直接造訪他的官方網站試試。
那麼透過 postman 或是其他你常用的工具測試看看拿到的資料結構會與 todo.controller.ts 內的結構相同,如下:
// src/modules/todos/todo.controller.ts
import {
Controller,
Get,
Post,
Put,
Delete,
Body,
Param,
HttpCode,
} from '@nestjs/common';
interface Todo {
id: number;
text: string;
active: boolean;
done: boolean;
}
let todos: Todo[] = [
'NestJS',
'GraphQL',
'Apollo',
'TypeScript',
'React',
'Redux',
'React Query',
'Angular',
'Vue',
'D3',
'Svelte',
'SolidJS',
'NextJS',
'AWS',
].map((text, index) => ({
id: index + 1,
text: `Learn ${text}`,
active: true,
done: false,
}));
@Controller('todos')
export class TodosController {
constructor() {}
@Get()
async index(): Promise<Todo[]> {
return todos.filter(({ active }) => active);
}
@Get(':id')
async show(@Param('id') id: string): Promise<Todo> {
return todos.find((todo) => todo.id === parseInt(id));
}
@Post()
async create(@Body() { text }: { text: string }): Promise<Todo> {
const todo = {
id: todos.length + 1,
text,
active: true,
done: false,
};
todos.push(todo);
return todo;
}
@Put(':id')
async update(@Param('id') id: string, @Body() data: Todo): Promise<Todo> {
todos = todos.map((todo) =>
todo.id === parseInt(id) ? { ...todo, ...data } : todo,
);
return data;
}
@Delete(':id')
@HttpCode(204)
async destroy(@Param('id') id: string): Promise<number> {
todos = todos.map((todo) =>
todo.id === parseInt(id) ? { ...todo, active: false } : todo,
);
return parseInt(id);
}
}
那麼知道了以上的資料結構,也確認了 server 是可以正常運行的話,下一篇我們就回歸正題來處理 RTK query 的部分了。